/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

/**
 * 跟着课程写的
 * 缺了很多 api 没补 比如 static reject, static all, static race, static allSettled, static any 等等
 * 后续再补
 */
const status_map = {
  pending: 'pending',
  fulfiled: 'fulfilled',
  rejected: 'rejected',
}

function toAsyncFunc(func) {
  setTimeout(func);
}

class MyPromise {
  status = status_map.pending;
  result = undefined;
  thenFnList = [];
  thenFailFnList = [];

  constructor(executorFn = () => {}) {
    try {
      executorFn(this.resolve, this.reject);
    } catch (executError) {
      this.reject(executError);
    }
  }

  static all(list) {
    let listDoneCount = 0;
    const promiseResult = [];
    return new MyPromise((resolve, reject) => {
      list.forEach((listItem, index) => {
        if (listItem instanceof MyPromise) {
          listItem.then(res => {
            promiseResult[index] = res;
            if (++listDoneCount === list.length) {
              resolve(promiseResult);
            }
          }, reject);
        } else {
          promiseResult[index] = listItem;
          if (++listDoneCount === list.length) {
            resolve(promiseResult);
          }
        }
      })
    });
  }

  static resolve(member) {
    return member instanceof MyPromise ? member : new MyPromise(reso => reso(member));
  }

  resolve = resolveResult => {
    if (this.status !== status_map.pending) return;
    this.status = status_map.fulfiled;
    this.result = resolveResult;
    while (this.thenFnList.length) this.thenFnList.shift()();
  }

  reject = rejectedError => {
    if (this.status !== status_map.pending) return;
    this.status = status_map.rejected;
    this.result = rejectedError;
    while (this.thenFailFnList.length) this.thenFailFnList.shift()();
  }

  then(thenFn = v => v, thenFailFn = e => { throw e }) {
    const nextPromise = new MyPromise((nextPromiseResolve, nextPromiseReject) => {
      if (this.status === status_map.fulfiled) {
        toAsyncFunc(() => {
          try {
            const nextPromiseResult = thenFn(this.result);
            this.resolveNextResult(nextPromise, nextPromiseResult, nextPromiseResolve, nextPromiseReject);
          } catch (thenFnError) {
            nextPromiseReject(thenFnError);
          }
        });
      } else if (this.status === status_map.pending) {
        typeof thenFn === 'function' && this.thenFnList.push(() => {
          toAsyncFunc(() => {
            try {
              const nextPromiseResult = thenFn(this.result);
              this.resolveNextResult(nextPromise, nextPromiseResult, nextPromiseResolve, nextPromiseReject);
            } catch (thenFnError) {
              nextPromiseReject(thenFnError);
            }
          });
        });
        typeof thenFailFn === 'function' && this.thenFailFnList.push(() => {
          toAsyncFunc(() => {
            try {
              const nextPromiseResult = thenFailFn(this.result);
              this.resolveNextResult(nextPromise, nextPromiseResult, nextPromiseResolve, nextPromiseReject);
            } catch (thenFnError) {
              nextPromiseReject(thenFnError);
            }
          });
        });
      } else {
        toAsyncFunc(() => {
          try {
            const nextPromiseResult = thenFailFn(this.result);
            this.resolveNextResult(nextPromise, nextPromiseResult, nextPromiseResolve, nextPromiseReject);
          } catch (thenFnError) {
            nextPromiseReject(thenFnError);
          }
        });
      }
    });
    return nextPromise;
  }

  catch = catchFn => {
    return this.then(undefined, catchFn);
  }

  finally(callback) {
    return this.then(value => {
      callback();
      return MyPromise.resolve(value);
      // return MyPromise.resolve(callback()).then(() => value);
    }, error => {
      callback();
      throw error;
      // return MyPromise.resolve(callback()).then(() => { throw error });
    });
  }

  resolveNextResult(promise, result, resolve, reject) {
    if (result === promise) {
      return reject('promise 循环引用: Chaining cycle detected for promise #<Promise>')
    } else if (result instanceof MyPromise) {
      return result.then(resolve, reject);
    } else {
      return resolve(result);
    }
  }
}

module.exports = MyPromise;
